/*
 * Copyright 2021 ZLG
 * All rights reserved.
 *
 */

#include "multi_cyc_fifo.h"

typedef enum _fifo_status
{
    FIFO_EMPTY, /* FIFO empty */
    FIFO_USING, /* FIFO using */
    FIFO_FULL   /* FIFO full */
} fifo_status_t;

typedef struct
{
    fifo_id_t ownerId;                  /* Owner FIFO ID */
    fifo_len_t fifoLen;                 /* FIFO len */
    fifo_len_t readAddr;                /* Read FIFO addr */
    fifo_len_t writeAddr;               /* Write FIFO addr */
    fifo_status_t fifoStatus;       /* FIFO status */
    uint8_t *p_startFifoAddr; /* Start FIFO addr */
    void *p_nextFifoList;          /* Next FIFO list */
} tFifoInfo;

#define STRUCT_LEN (20u) /* Every FIFO struct used space */
#define TOTAL_BYTES ((STRUCT_LEN) * (FIFO_NUM) + TOTAL_FIFO_BYTES) /* config total bytes */

static uint8_t s_fifo[TOTAL_BYTES] = {0};    /* Total FIFO len */
static tFifoInfo *sp_listHeader = (tFifoInfo *)0u; /* Manage list FIFO header */
static fifo_len_t s_cleanFifoLen = TOTAL_BYTES;           /* Can used FIFO len */

/**********************************************************
** Description      :   Add counter
** Input Parameter  :   i_xFifoLen total FIFO len
** Modify Parameter :   m_pxCounter need modify counter  Read/Write counter
***********************************************************/
#define AddCounter(i_xFifoLen, m_pxCounter)\
    do{\
        (*(m_pxCounter))++;\
        if(*(m_pxCounter) >= (i_xFifoLen))\
        {\
            *(m_pxCounter) -= (i_xFifoLen);\
        }\
    }while(0)

/* Add write counter used in write FIFO */
#define AddWriteCounter(m_pstNode)\
    do{\
        AddCounter(m_pstNode->fifoLen, &(m_pstNode->writeAddr));\
    }while(0)

/* Check and change current  write FIFO status */
#define CheckAndChangeWriteFIFOStatus(m_pstNode) \
    do{\
        DisableAllInterrupts();\
        if((m_pstNode)->writeAddr == (m_pstNode)->readAddr)\
        {\
            (m_pstNode)->fifoStatus = FIFO_FULL;\
        }\
        else\
        {\
            (m_pstNode)->fifoStatus = FIFO_USING;\
        }\
        EnableAllInterrupts();\
    }while(0u)

/* Add read counter used in read FIFO */
#define AddReadCounter(m_pstNode)\
    do{\
        AddCounter(m_pstNode->fifoLen, &(m_pstNode->readAddr));\
    }while(0)

/* Check and change current read FIFO status */
#define CheckAndChangeReadFIFOStatus(m_pstNode) \
    do{\
        DisableAllInterrupts();\
        if((m_pstNode)->writeAddr == (m_pstNode)->readAddr)\
        {\
            (m_pstNode)->fifoStatus = FIFO_EMPTY;\
        }\
        else\
        {\
            (m_pstNode)->fifoStatus = FIFO_USING;\
        }\
        EnableAllInterrupts();\
    }while(0u)


/* Get FIFO list header */
#define GetListHeader(o_psListHeader)\
    do{\
        (o_psListHeader) = sp_listHeader;\
    }while(0)

static void AddInList(tFifoInfo *i_pstFifoNode, tFifoInfo **m_ppstHeader, muilt_cyc_fifo_error_code_t *o_peAddStatus);
static void FindFifo(fifo_id_t fifoId, tFifoInfo **o_ppstNode, muilt_cyc_fifo_error_code_t *o_peFindStatus);

/**********************************************************
**  Function Name       :   MCF_ApplyFifo
**  Description         :   Apply a FIFO
**  Input Parameter     :   fifoLen need apply FIFO len
                            fifoId FIFO ID. Use find this FIFO.
**  Modify Parameter    :   none
**  Output Parameter    :   p_applyStatus apply status. If apply success kMultiCycFiFo_Error_NONE, else kMultiCycFiFo_Error_XXX
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2013-3-27
**********************************************************/
void MCF_ApplyFifo(fifo_len_t fifoLen, fifo_len_t fifoId, muilt_cyc_fifo_error_code_t *p_applyStatus)
{
    tFifoInfo *p_node = (tFifoInfo *)0u;
    fifo_len_t nodeNeedSpace = 0u;
    uint32_t cleanFIFOLenTmp = 0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == p_applyStatus)
    {
        return;
    }

    /* Confirm apply FIFO header start align as 4Bytes */
    cleanFIFOLenTmp = (uint32_t)(&s_fifo[TOTAL_BYTES - s_cleanFifoLen]) & 0x03u;

    if ((fifoLen + STRUCT_LEN + cleanFIFOLenTmp) > s_cleanFifoLen)
    {
        *p_applyStatus = kMultiCycFiFo_Error_OVER_MAX;
        return;
    }

#endif
    FindFifo(fifoId, &p_node, p_applyStatus);

    if (kMultiCycFiFo_Error_NONE == *p_applyStatus)   /* Note if kMultiCycFiFo_Error_NONE that means ID have registered. */
    {
        *p_applyStatus = kMultiCycFiFo_Error_REGISTERED_SECOND;
        return;
    }

    if (cleanFIFOLenTmp)
    {
        s_cleanFifoLen -= cleanFIFOLenTmp;
    }

    p_node = (tFifoInfo *)(&s_fifo[TOTAL_BYTES - s_cleanFifoLen]);
    p_node->ownerId = fifoId;
    p_node->p_nextFifoList = (void *)0u;
    p_node->fifoLen = fifoLen;
    p_node->readAddr = 0u;
    p_node->writeAddr = 0u;
    p_node->p_startFifoAddr = (uint8_t *)((tFifoInfo *)(&s_fifo[TOTAL_BYTES - s_cleanFifoLen]) + 1u);
    p_node->fifoStatus = FIFO_EMPTY;
    nodeNeedSpace = (fifo_len_t)((uint8_t *)((tFifoInfo *)(&s_fifo[TOTAL_BYTES - s_cleanFifoLen]) + 1u) -
                                 (uint8_t *)(&s_fifo[TOTAL_BYTES - s_cleanFifoLen]));
    nodeNeedSpace += fifoLen;
    s_cleanFifoLen -= nodeNeedSpace;
    AddInList(p_node, &sp_listHeader, p_applyStatus);
}

/**********************************************************
**  Function Name       :   MCF_WriteDataInFifo
**  Description         :   write data in FIFO.
**  Input Parameter     :   fifoId   FIFO ID
                            p_writeDataBuffer Need write data buffer
                            writeDatalen  write data len
**  Modify Parameter    :   none
**  Output Parameter    :   p_writeStatus write data status. If successful kMultiCycFiFo_Error_NONE, else kMultiCycFiFo_Error_XX
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2013-3-27
**********************************************************/
void MCF_WriteDataInFifo(fifo_id_t fifoId,
                         uint8_t *p_writeDataBuffer,
                         fifo_len_t writeDatalen,
                         muilt_cyc_fifo_error_code_t *p_writeStatus)
{
    tFifoInfo *p_node = (tFifoInfo *)0u;
    fifo_len_t index = 0u;
    fifo_len_t canWriteTotal = 0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == p_writeStatus)
    {
        return;
    }

    if ((uint8_t *)0u == p_writeDataBuffer)
    {
        *p_writeStatus = kMultiCycFiFo_Error_POINTER_NULL;
        return;
    }

#endif
    MCF_GetCanWriteLen(fifoId, &canWriteTotal, p_writeStatus);

    if (kMultiCycFiFo_Error_NONE != *p_writeStatus)
    {
        return;
    }

    if (writeDatalen > canWriteTotal)
    {
        *p_writeStatus = kMultiCycFiFo_Error_OVER_MAX;
        return;
    }

    FindFifo(fifoId, &p_node, p_writeStatus);

    if (kMultiCycFiFo_Error_NONE != *p_writeStatus)
    {
        return;
    }

    for (index = 0u; index < writeDatalen; index++)
    {
        (p_node->p_startFifoAddr)[p_node->writeAddr] = p_writeDataBuffer[index];
        AddWriteCounter(p_node);
    }

    /* Check and change write FIFO status */
    CheckAndChangeWriteFIFOStatus(p_node);
    *p_writeStatus = kMultiCycFiFo_Error_NONE;
}

/**********************************************************
**  Function Name       :   MCF_ReadDataFromFifo
**  Description         :   Read data from FIFO.
**  Input Parameter     :   fifoId need read FIFO
                            needReadDataLen read data len
**  Modify Parameter    :   none
**  Output Parameter    :   p_readDataBuf need read data buffer.
                            p_actualReadDataLen need read data len
                            readStatus read status. If read successful kMultiCycFiFo_Error_NONE, else kMultiCycFiFo_Error_XXX
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2013-3-27
**********************************************************/
void MCF_ReadDataFromFifo(fifo_id_t fifoId, fifo_len_t needReadDataLen,
                          uint8_t *p_readDataBuf,
                          fifo_len_t *p_actualReadDataLen,
                          muilt_cyc_fifo_error_code_t *readStatus)
{
    tFifoInfo *p_node = (tFifoInfo *)0u;
    fifo_len_t index = 0u;
    fifo_len_t canReadTotal = 0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == readStatus)
    {
        return;
    }

    if ((uint8_t *)0u == p_readDataBuf ||
            (fifo_len_t *)0u == p_actualReadDataLen ||
            (fifo_len_t)0u == needReadDataLen )
    {
        *readStatus = kMultiCycFiFo_Error_POINTER_NULL;
        return;
    }

#endif
    MCF_GetCanReadLen(fifoId, &canReadTotal, readStatus);

    if (kMultiCycFiFo_Error_NONE != *readStatus)
    {
        return;
    }

    FindFifo(fifoId, &p_node, readStatus);

    if (kMultiCycFiFo_Error_NONE != *readStatus)
    {
        return;
    }

    canReadTotal = canReadTotal > needReadDataLen ? needReadDataLen : canReadTotal;
    *p_actualReadDataLen = canReadTotal;

    for (index = 0u; index < canReadTotal; index++)
    {
        p_readDataBuf[index] = (p_node->p_startFifoAddr)[p_node->readAddr] ;
        AddReadCounter(p_node);
    }

    /* Check and change read FIFO status */
    CheckAndChangeReadFIFOStatus(p_node);
    *readStatus = kMultiCycFiFo_Error_NONE;
}

/**********************************************************
**  Function Name       :   MCF_GetCanReadLen
**  Description         :   Get FIFO have data.
**  Input Parameter     :   fifoId FIFO ID
**  Modify Parameter    :   none
**  Output Parameter    :   p_canReadLen how much data can read.
                            p_getStatus get status. If get successful kMultiCycFiFo_Error_NONE, else kMultiCycFiFo_Error_XXX
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2013-3-27
**********************************************************/
void MCF_GetCanReadLen(fifo_id_t fifoId, fifo_len_t *p_canReadLen, muilt_cyc_fifo_error_code_t *p_getStatus)
{
    tFifoInfo *p_node = (tFifoInfo *)0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == p_getStatus)
    {
        return;
    }

    if ((fifo_len_t *)0u == p_canReadLen)
    {
        *p_getStatus = kMultiCycFiFo_Error_POINTER_NULL;
        return;
    }

#endif
    FindFifo(fifoId, &p_node, p_getStatus);

    if (kMultiCycFiFo_Error_NONE != *p_getStatus)
    {
        return;
    }

    if (FIFO_USING == p_node->fifoStatus)
    {
        *p_canReadLen = (p_node->readAddr > p_node->writeAddr) ?
                        (p_node->fifoLen - p_node->readAddr + p_node->writeAddr) :
                        (p_node->writeAddr - p_node->readAddr);
    }
    else if (FIFO_FULL == p_node->fifoStatus)
    {
        *p_canReadLen = p_node->fifoLen;
    }
    else
    {
        *p_canReadLen = (fifo_len_t)0u;
    }

    *p_getStatus = kMultiCycFiFo_Error_NONE;
}

/**********************************************************
**  Function Name       :   MCF_GetCanWriteLen
**  Description         :   Get can write data.
**  Input Parameter     :   fifoId FIFO ID
**  Modify Parameter    :   none
**  Output Parameter    :   p_canWriteLen how much data can write.
                            p_getStatus get data status. If get successful kMultiCycFiFo_Error_NONE, else kMultiCycFiFo_Error_XX
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2013-3-27
**********************************************************/
void MCF_GetCanWriteLen(fifo_id_t fifoId, fifo_len_t *p_canWriteLen, muilt_cyc_fifo_error_code_t *p_getStatus)
{
    tFifoInfo *p_node = (tFifoInfo *)0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == p_getStatus)
    {
        return;
    }

    if ((fifo_len_t *)0u == p_canWriteLen)
    {
        *p_getStatus = kMultiCycFiFo_Error_POINTER_NULL;
        return;
    }

#endif
    FindFifo(fifoId, &p_node, p_getStatus);

    if (kMultiCycFiFo_Error_NONE != *p_getStatus)
    {
        return;
    }

    if (FIFO_USING == p_node->fifoStatus)
    {
        *p_canWriteLen = (p_node->readAddr > p_node->writeAddr) ?
                         (p_node->readAddr - p_node->writeAddr) :
                         (p_node->fifoLen + p_node->readAddr - p_node->writeAddr);
    }
    else if (FIFO_EMPTY == p_node->fifoStatus)
    {
        *p_canWriteLen = p_node->fifoLen;
    }
    else
    {
        *p_canWriteLen = (fifo_len_t)0u;
    }

    *p_getStatus = kMultiCycFiFo_Error_NONE;
}

/**********************************************************
**  Function Name       :   AddInList
**  Description         :   Add node in list
**  Input Parameter     :   i_pstFifoNode need add in list node.
**  Modify Parameter    :   m_ppstHeader header list
**  Output Parameter    :   o_peAddStatus add status
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2013-3-26
**********************************************************/
static void AddInList(tFifoInfo *i_pstFifoNode, tFifoInfo **m_ppstHeader, muilt_cyc_fifo_error_code_t *o_peAddStatus)
{
    tFifoInfo *p_nodeTmp = (tFifoInfo *)0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == o_peAddStatus)
    {
        return;
    }

    if ((tFifoInfo **)0u == m_ppstHeader || (tFifoInfo *)0u == i_pstFifoNode)
    {
        *o_peAddStatus = kMultiCycFiFo_Error_POINTER_NULL;
        return;
    }

#endif

    if ((tFifoInfo *)0u == *m_ppstHeader)
    {
        *m_ppstHeader = i_pstFifoNode;
        *o_peAddStatus = kMultiCycFiFo_Error_NONE;
        return;
    }

    p_nodeTmp = *m_ppstHeader;

    while ((void *)0u != p_nodeTmp->p_nextFifoList)
    {
        if (i_pstFifoNode == p_nodeTmp)
        {
            *o_peAddStatus = kMultiCycFiFo_Error_REGISTERED_SECOND;
            return;
        }

        p_nodeTmp = (tFifoInfo *)(p_nodeTmp->p_nextFifoList);
    }

    p_nodeTmp->p_nextFifoList = (void *)i_pstFifoNode;
    i_pstFifoNode->p_nextFifoList = (muilt_cyc_fifo_error_code_t *)0u;
    *o_peAddStatus = kMultiCycFiFo_Error_NONE;
}

/**********************************************************
**  Function Name       :   FindFifo
**  Description         :   Find FIFO in FIFO list.
**  Input Parameter     :   FindFifo
**  Modify Parameter    :   none
**  Output Parameter    :   o_ppstNode
                            o_peFindStatus
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2013-3-26
**********************************************************/
static void FindFifo(fifo_id_t fifoId, tFifoInfo **o_ppstNode, muilt_cyc_fifo_error_code_t *o_peFindStatus)
{
    tFifoInfo *p_node = (tFifoInfo *)0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == o_peFindStatus)
    {
        return;
    }

    if ((tFifoInfo **)0u == o_ppstNode)
    {
        *o_peFindStatus = kMultiCycFiFo_Error_POINTER_NULL;
        return;
    }

#endif
    GetListHeader(p_node);

    while ((tFifoInfo *)0u != p_node)
    {
        if (fifoId == p_node->ownerId)
        {
            *o_peFindStatus = kMultiCycFiFo_Error_NONE;
            *o_ppstNode = p_node;
            return;
        }

        p_node = (tFifoInfo *)p_node->p_nextFifoList;
    }

    *o_peFindStatus = kMultiCycFiFo_Error_NO_NODE;
}

/**********************************************************
**  Function Name       :   MCF_ClearFIFO
**  Description         :   Clear FIFO, set read pointer equal write pointer
**  Input Parameter     :   fifoId FIFO ID
**  Modify Parameter    :   none
**  Output Parameter    :   p_getStatus get data status. If get successful kMultiCycFiFo_Error_NONE, else kMultiCycFiFo_Error_XX
**  Return Value        :   none
**  Version             :   v00.00.01
**  Author              :   Tomlin
**  Created Date        :   2019-6-18
**********************************************************/
void MCF_ClearFIFO(fifo_id_t fifoId, muilt_cyc_fifo_error_code_t *p_getStatus)
{
    tFifoInfo *p_node = (tFifoInfo *)0u;
#ifdef SAFE_LEVEL_O3

    if ((muilt_cyc_fifo_error_code_t *)0u == p_getStatus)
    {
        return;
    }

#endif
    FindFifo(fifoId, &p_node, p_getStatus);

    if (kMultiCycFiFo_Error_NONE != *p_getStatus)
    {
        return;
    }

    DisableAllInterrupts();
    p_node->fifoStatus = FIFO_EMPTY;
    p_node->readAddr = p_node->writeAddr;
    EnableAllInterrupts();
    *p_getStatus = kMultiCycFiFo_Error_NONE;
}

/* -------------------------------------------- END OF FILE -------------------------------------------- */
